/**
 * 
 */
package edu.vanderbilt.vshare.multicast;

import java.util.HashMap;
import java.util.Iterator;

/**
 * central storage for all files sent or received by MulticastShare
 * @author jedmondson
 *
 */
public class FileStore {

	/**
	 * Constructor
	 */
	FileStore() {
		
	}
	
	/**
	 * Add a file to the store
	 *
	 * @param     filename       name of the file to add
	 * @return                   true if file was added. false otherwise
	 */

	public boolean add (String filename)
	{		
		int fileid;
		
		// if we've already added this filename, just stop
		if (name_map.containsKey (filename))
		{
			return false;
		}
		
		// start with the size of the map, increment until we
		// find a unique fileid for this file
		for (fileid = map.size(); map.containsKey(fileid); ++fileid);
		
		return add (fileid, filename);
	}

	/**
	 * Add a file to the store
	 *
	 * @param     fileid         id that the file should assume (WARNING
	 *                           make sure that the fileid is not already taken)
	 * @param     filename       name of the file to add
	 * @return                   true if file was added. false otherwise
	 */

	public boolean add (int fileid, String filename)
	{
		FileStruct file = new FileStruct (filename);

		if (!name_map.containsKey(filename))
		{
			if (!file.hasFailed())
			{
				// add the file id and the file name to our store
				map.put(fileid, file);
				name_map.put(filename, fileid);

				System.out.println ("Successfully added fileid " + fileid +
				" with the following metadata: ");
				String metadata = Utility.trimZeros(new String (file.getMeta()));
				System.out.println (metadata);

				return true;
			}
		}
		else
		{
			System.out.println ("FileStore: ERROR: file " + filename + 
					" already exists at fileid " + name_map.get(filename));
		}
		
		return false;
	}

	/**
	 * Retrieve file info from metadata and add the file to the store
	 * 
	 * @param     fileid         id that the file should assume (WARNING
	 *                           make sure that the fileid is not already taken)
	 *
	 * @param     metadata       metadata payload from server
	 * @return                   true if file was added. false otherwise
	 */
	
	public boolean add (int fileid, byte[] metadata)
	{
		FileStruct file = new FileStruct ();
		file.constructFromMeta(metadata);
		
		if (!file.hasFailed())
		{
			// add the file id and the file name to our store
			map.put(fileid, file);
			name_map.put(file.getName(), fileid);

			if (!name_map.containsKey(file.getName()))
			{
				System.out.println ("Successfully added fileid " + fileid +
				" named " + file.getName() + " from metadata ");
			}
			else
			{
				System.out.println ("FileStore: ERROR: file " + file.getName() + 
						" already exists at fileid " + name_map.get(file.getName()));
			}
		}
		
		return file.hasFailed();
	}

	/**
	 * Update the file with a new packet payload
	 * 
	 * @param     fileid         id of the file to update
	 * @param     number         payload identifier
	 * @param     payload        payload to set at the identifier
	 */
	
	public void add (int fileid, int number, byte [] payload)
	{
		if (map.containsKey(fileid))
		{
			FileStruct file = map.get(fileid);
			file.setPayload(number, payload, false);
		}
	}

	/**
	 * Remove a file from the store. There are very few reasons to do
	 * this other than space constraints.
	 * 
	 * @param     fileid         id of the file to remove
	 */
	
	public void remove (int fileid)
	{
		// starting to wonder if this should really be allowed.
		// it should at least check to see if any thread is using/
		// broadcasting this fileid on server side.
		
		// on client side, this operation is valid as is.
		map.remove(fileid);
	}

	/**
	 * Retrieve a complete file record by fileid
	 * 
	 * @param     fileid         id of the file to get
	 * @return                   file record being used by all threads
	 */
	
	public FileStruct get (int fileid)
	{
		if (map.containsKey(fileid))
			return map.get(fileid);
		
		return null;
	}

	/**
	 * Retrieve a complete file record by filename
	 * 
	 * @param     filename       name of the file to get
	 * @return                   file record being used by all threads
	 */
	
	public FileStruct get (String filename)
	{
		return map.get(name_map.get(filename));
	}

	/**
	 * Retrieve the unique file id for a filename. Use only after
	 *  contains(filename). Otherwise, behavior is undefined.
	 * 
	 * @param     filename       name of the file to get id of
	 * @return                   the file id of this filename
	 */
	
	public int getFileId (String filename)
	{
		return name_map.get(filename);
	}

	/**
	 * Check to see if the fileid is valid
	 * 
	 * @param     fileid         unique file identifier
	 * @return                   true if the fileid has been assigned
	 */
	
	public boolean contains (int fileid)
	{
		return map.containsKey(fileid);
	}

	/**
	 * Check to see if a file with the filename has been created
	 * 
	 * @param     filename       name of the file to check on
	 * @return                   true if the filename exists in the store
	 */
	
	public boolean contains (String filename)
	{
		return name_map.containsKey(filename);
	}

	/**
	 * Check to see if full file has been received and validated
	 * 
	 * @param     filename       name of the file to check on
	 * @return                   true if the file is complete
	 */
	
	public boolean isComplete (String filename)
	{
		if(contains (filename))
		{
			return isComplete (name_map.get(filename));
		}
		
		return false;
	}

	/**
	 * Check to see if full file has been received and validated
	 * 
	 * @param     fileid         id of the file to check on
	 * @return                   true if the file is complete
	 */
	
	public boolean isComplete (int fileid)
	{
		if(contains (fileid))
		{
			FileStruct file = map.get(fileid);
			return file.getIncompletes() == 0;
		}
		
		return false;
	}

	/**
	 * Retrieve as much of the file as possible, starting from some
	 * offset
	 * 
	 * @param     filename       name of the file to retrieve bytes of
	 * @param     offset         packet offset into the file. Note that
	 *                           this is NOT the byte offset. This is packetSize *
	 *                           offset into the file. 0 indicates start of file.
	 * @return                   contiguous bytes received from and including offset
	 */
	
	public byte [] getBytes (String filename, int offset)
	{
		if (contains (filename))
		{
			int fileid = name_map.get(filename);
			return getBytes (fileid, offset);
		}
		
		return null;
	}

	/**
	 * Retrieve as much of the file as possible, starting from some
	 * offset
	 * 
	 * @param     fileid         id of the file to retrieve bytes of
	 * @param     offset         packet offset into the file. Note that
	 *                           this is NOT the byte offset. This is packetSize *
	 *                           offset into the file. 0 indicates start of file.
	 * @return                   contiguous bytes received from and including offset
	 */
	
	public byte [] getBytes (int fileid, int offset)
	{
		if(contains (fileid))
		{
			return map.get(fileid).getBytes(offset);
		}
		
		return null;
	}

	/**
	 * Retrieve the number of packets that have not been received yet for a
	 * specified fileid
	 * 
	 * @param     fileid         id of the file to check on
	 * @return                   number of packets that have not been received
	 */
	
	public int remainingPackets (int fileid)
	{
		if(contains (fileid))
		{
			return map.get(fileid).getIncompletes();
		}
		
		return 0;
	}

	/**
	 * Retrive the metadata for the file. If -1 is provided, return
	 * the file repo meta data.
	 * 
	 * @param     fileid         id of the file to check on
	 * @return                   metadata for the file store
	 */
	
	public byte [] getMeta (int fileid)
	{
		if(contains (fileid))
		{
			return map.get(fileid).getMeta();
		}
		
		// user is looking for file repo information
		if(fileid == -1)
		{
			if (meta != null)
				return meta;
			
			Iterator<Integer> i = map.keySet ().iterator ();
			Integer key = null;
			FileStruct value = null;
			StringBuilder builder = new StringBuilder ();
			
			builder.append ("<files>\n");
			
			while (i.hasNext ())
			{
				key = (Integer)i.next ();
				
				value = map.get (key);
				
				builder.append ("<file>\n");
				builder.append ("  <id>" + key + "</id>\n");
				builder.append ("  <name>" + value.getName () + "</name>\n");
				builder.append ("  <type>" + value.getContentType () + "</type>\n");
				builder.append ("  <length>" + value.getLength () + "</length>\n");
				builder.append ("</file>\n");
				
			}
			
			builder.append ("</files>\n");

			// note that this is NOT aligned to a packet size
			meta = builder.toString().getBytes ();
			
			return meta;
		}
		
		// fileid was unrecognized
		return null;
	}
	
	private HashMap <Integer, FileStruct> map = new HashMap <Integer, FileStruct> ();
	private HashMap <String, Integer> name_map = new HashMap <String, Integer> ();
	
	byte [] meta = null;
}
